
<!DOCTYPE HTML>
<html lang="zh-hans" >
    <head>
        <meta charset="UTF-8">
        <title>7 概束和泛型编程 · C++导览 第二版 简体中文版</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="description" content="">
        <meta name="generator" content="HonKit 5.1.1">
        <meta name="author" content="Windsting">
        
        
    
    <link rel="stylesheet" href="gitbook/style.css">

    
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-forkmegithub/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@honkit/honkit-plugin-highlight/website.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
                
            
        

    

    
        
        <link rel="stylesheet" href="styles/website.css">
        
    
        
    
        
    
        
    
        
    
        
    

        
    
    
    <meta name="HandheldFriendly" content="true"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
    <link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">

    
    <link rel="next" href="ch08.html" />
    
    
    <link rel="prev" href="ch06.html" />
    

    </head>
    <body>
        
<div class="book honkit-cloak">
    <div class="book-summary">
        
            
<div id="book-search-input" role="search">
    <input type="text" placeholder="输入并搜索" />
</div>

            
                <nav role="navigation">
                


<ul class="summary">
    
    
    
        
        <li>
            <a href="https://github.com/windsting/a-tour-of-cpp-2nd-cn" target="_blank" class="custom-link">Github Link</a>
        </li>
    
    

    
    <li class="divider"></li>
    

    
        
        
    
        <li class="chapter " data-level="1.1" data-path="translation_note.html">
            
                <a href="translation_note.html">
            
                    
                    译者言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.2" data-path="./">
            
                <a href="./">
            
                    
                    前言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.3" data-path="ch01.html">
            
                <a href="ch01.html">
            
                    
                    1 基础知识
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.4" data-path="ch02.html">
            
                <a href="ch02.html">
            
                    
                    2 用户定义类型
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.5" data-path="ch03.html">
            
                <a href="ch03.html">
            
                    
                    3 模块化
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.6" data-path="ch04.html">
            
                <a href="ch04.html">
            
                    
                    4 类
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.7" data-path="ch05.html">
            
                <a href="ch05.html">
            
                    
                    5 基本操作
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.8" data-path="ch06.html">
            
                <a href="ch06.html">
            
                    
                    6 模板
            
                </a>
            

            
        </li>
    
        <li class="chapter active" data-level="1.9" data-path="ch07.html">
            
                <a href="ch07.html">
            
                    
                    7 概束和泛型编程
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.10" data-path="ch08.html">
            
                <a href="ch08.html">
            
                    
                    8 标准库概览
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.11" data-path="ch09.html">
            
                <a href="ch09.html">
            
                    
                    9 字符串和正则表达式
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.12" data-path="ch10.html">
            
                <a href="ch10.html">
            
                    
                    10 输入输出
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.13" data-path="ch11.html">
            
                <a href="ch11.html">
            
                    
                    11 容器
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.14" data-path="ch12.html">
            
                <a href="ch12.html">
            
                    
                    12 算法
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.15" data-path="ch13.html">
            
                <a href="ch13.html">
            
                    
                    13 实用功能
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.16" data-path="ch14.html">
            
                <a href="ch14.html">
            
                    
                    14 数值
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.17" data-path="ch15.html">
            
                <a href="ch15.html">
            
                    
                    15 并发
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.18" data-path="ch16.html">
            
                <a href="ch16.html">
            
                    
                    16 历史及兼容性
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.19" data-path="idx.html">
            
                <a href="idx.html">
            
                    
                    索引
            
                </a>
            

            
        </li>
    

    

    <li class="divider"></li>

    <li>
        <a href="https://github.com/honkit/honkit" target="blank" class="gitbook-link">
            本书使用 HonKit 发布
        </a>
    </li>
</ul>


                </nav>
            
        
    </div>

    <div class="book-body">
        
            <div class="body-inner">
                
                    

<div class="book-header" role="navigation">
    

    <!-- Title -->
    <h1>
        <i class="fa fa-circle-o-notch fa-spin"></i>
        <a href="." >7 概束和泛型编程</a>
    </h1>
</div>




                    <div class="page-wrapper" tabindex="-1" role="main">
                        <div class="page-inner">
                            
<div id="book-search-results">
    <div class="search-noresults">
    
                                <section class="normal markdown-section">
                                

    
        
                                <p><a class="en-page-number" id="93"></a></p>
<div class="chapter-number"><p class="chapter-number">7</p></div>

<h1 id="concepts-and-generic-programming">概束和泛型编程 </h1>
<blockquote>
<p>编程：</p>
<p>你得从感兴趣的算法入手。</p>
<p>– <span title="出自一份意大利杂志《INFOMEDIA》对 Alexander A. Stepanov 的访谈录，访谈内容全文位于 http://www.stlport.org/resources/StepanovUSA.html">Alex Stepanov</span><sup><a href="#fn_1" id="reffn_1">1</a></sup></p>
</blockquote>
<h2 id="7.1">7.1 导言 </h2>
<p>模板是干嘛用的？换句话说，使用模板对什么样的编程技术更有效？它提供了：</p>
<ul>
<li>把类型（以及值和模板）作为参数无损传递。这意味着内联的绝佳的机遇，
  也是当前编译器实现着重发力的部分。</li>
<li>在实例化期，从各种语境把信息搜罗编排起来的机会。这意味着优化的机遇。</li>
<li>把常量作为参数传递的能力。这意味着编译期计算的能力。</li>
</ul>
<p>换句话说，模板提供了强大的机制，用于编译期计算、类型操控，并导向紧凑高效的代码。
谨记，类型（类）即包含代码(§6.3.2)又包含值(§6.2.2)。</p>
<p><a class="en-page-number" id="94"></a></p>
<p>首要的也是最常见的模板用途，是支持<em>泛型编程（generic programming）</em>，
也就是关注于通用算法设计、实现和应用的编程。
此处，“通用”的意思是：算法的设计可接纳很广泛的类型，
只要它们符合算法在参数方面的要求即可。
搭配概束，模板是C++对泛型编程的主力支援。
模板提供了（编译期的）参数化多态。</p>
<h2 id="7.2">7.2 概束(Concept)（C++20） </h2>
<p>考虑来自 §6.3.1 的 <code>sum()</code>：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Seq, <span class="hljs-keyword">typename</span> Num&gt;
Num <span class="hljs-title">sum</span><span class="hljs-params">(Seq s, Num v)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span>&amp; x : s)
        v+=x;
    <span class="hljs-keyword">return</span> v;
}
</code></pre>
<p>它可以针对任何支持<code>begin()</code>和<code>end()</code>的数据结构调用，
以便 区间-<code>for</code>循环 能运行。
这类数据结构包括标准库的<code>vector</code>、<code>list</code>和<code>map</code>。
另外，该数据结构的元素类型在使用方面有限制：该类型必须能够与<code>Value</code>参数相加。
可行的例子有<code>int</code>、<code>double</code>和<code>Matrix</code>（对任何靠谱的<code>Matrix</code>定义而言）。
我们说<code>sum()</code>在两个维度上是通用的：
用于存储元素的数据结构类型（“是个序列(sequence)”），以及元素的类型。</p>
<p>因此<code>sum()</code>要求其第一个模板参数是某种序列，其第二个模板参数是某种数字。
我们把这种要求称为<em>概束（concept）</em>。</p>
<p>对概束的支持尚未进入ISO C++，
但已经是一条ISO技术细则（Technical Specification）[ConceptsTS]。
某些编译器实现里已经有所应用，因此，
尽管其细节很可能会变动，尽管还要等几年才能进入生产环境代码，
我依然要冒险在这里推荐它。</p>
<h3 id="7.2.1">7.2.1 概束应用 </h3>
<p>多数模板参数必须符合特定需求，以便模板能通过编译并生成正确的代码。
就是说，多数模板都是受限模板(§6.2.1)。
类型名称引入符号<code>typename</code>是最松散的约束，仅要求该参数是个类型。
通常仍有改进空间，重新考虑<code>sum()</code>例子：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Sequence Seq, Number Num&gt;
Num <span class="hljs-title">sum</span><span class="hljs-params">(Seq s, Num v)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span>&amp; x : s)
        v+=x;
    <span class="hljs-keyword">return</span> v;
}
</code></pre>
<p>这样就明确多了。一旦我们定义了概束<code>Sequence</code>和<code>Number</code>的意义，
编辑器仅根据<code>sum()</code>的接口就能驳回错误的调用，而无需再检查其函数体。
这改善了错误报告。</p>
<p><a class="en-page-number" id="95"></a></p>
<p>但是，这份<code>sum()</code>接口的细节并不完善：
我“忘了”说，需要把<code>Sequence</code>的元素于<code>Number</code>相加。
可以这样做：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;Sequence Seq, Number Num&gt;
    <span class="hljs-keyword">requires</span> Arithmetic&lt;Value_type&lt;Seq&gt;,Num&gt;
<span class="hljs-function">Num <span class="hljs-title">sum</span><span class="hljs-params">(Seq s, Num n)</span></span>;
</code></pre>
<p>序列的<code>Value_type</code>是序列中元素的类型。<code>Arithmetic&lt;X,Y&gt;</code>是个规则，
指明我们可以对<code>X</code>和<code>Y</code>类型的数字做算术运算。
这让我们避免了给<code>vector&lt;string&gt;</code>或者<code>vector&lt;int*&gt;</code>计算<code>sum()</code>的尝试，
却还能接受<code>vector&lt;int&gt;</code>以及<code>vector&lt;complex&lt;double&gt;&gt;</code>。</p>
<p>在上例中，我们仅需要<code>+=</code>运算，但处于简化及灵活性，不该把模板参数限制的太紧。
具体来说，我们后续可能用<code>+</code>和<code>=</code>，而非<code>+=</code>来实现<code>sum()</code>，
到那时就会庆幸用了更通用的规则（此处是<code>Arithmetic</code>），
而没有把需求缩窄到“可以<code>+=</code>“。</p>
<p>像第一个使用概束的<code>sum()</code>那样，指明部分细节已经很有用了。
如果细节不够完整，那么某些错误只能等到实例化期才能发现。
无论如何，指明部分细节就很有帮助了，它表明了意图，对平缓的增量开发至关重要，
也就是我们未能一开始就认清所有需求的情形。
有了完善的概束库的帮助，初步的规划就将近乎完善。</p>
<p>不难猜到，<code>requires Arithmetic&lt;Value_type&lt;Seq&gt;,Num&gt;</code>
被称为<code>requirements</code>子句。
<code>template&lt;Sequence Seq&gt;</code>写法仅仅是显式<code>requires Sequence&lt;Seq&gt;</code>应用的简写。
如果喜欢详尽的方式，可以写出以下等价形式：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Seq, <span class="hljs-keyword">typename</span> Num&gt;
    <span class="hljs-keyword">requires</span> Sequence&lt;Seq&gt; &amp;&amp; Number&lt;Num&gt; &amp;&amp; Arithmetic&lt;Value_type&lt;Seq&gt;,Num&gt;
<span class="hljs-function">Num <span class="hljs-title">sum</span><span class="hljs-params">(Seq s, Num n)</span></span>;
</code></pre>
<p>另一方面，也可以用与以上两个写法的等价形式这样写：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;Sequence Seq, Arithmetic&lt;Value_type&lt;Seq&gt;&gt; Num&gt;
<span class="hljs-function">Num <span class="hljs-title">sum</span><span class="hljs-params">(Seq s, Num n)</span></span>;
</code></pre>
<p>无论使用哪种形式，在设计模板的时候，
确保模板参数具有语意方面的合理约束是很重要的(§7.2.4)。</p>
<h3 id="7.2.2">7.2.2 基于概束的重载 </h3>
<p>在妥善声明模板接口后，可以基于其属性进行重载，这与函数重载极其类似。
考虑一下标准库函数<code>advance()</code>(§12.3)的简化版本，它推进迭代器：</p>
<p><a class="en-page-number" id="96"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Forward_iterator Iter&gt;
<span class="hljs-type">void</span> <span class="hljs-title">advance</span><span class="hljs-params">(Iter p, <span class="hljs-type">int</span> n)</span> <span class="hljs-comment">// 将p向前移动n个元素</span>
</span>{
    <span class="hljs-keyword">while</span> (n--)
        ++p;                <span class="hljs-comment">// 前向迭代器可以++，但不能+或+=</span>
}

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Random_access_iterator Iter&gt;
<span class="hljs-type">void</span> <span class="hljs-title">advance</span><span class="hljs-params">(Iter p, <span class="hljs-type">int</span> n)</span> <span class="hljs-comment">// 将p向前移动n个元素</span>
</span>{
    p+=n;                   <span class="hljs-comment">// 随机访问迭代器可以+=</span>
}
</code></pre>
<p>编译器会选择参数对需求匹配最紧密的模板。本例中，<code>list</code>仅有前向迭代器，
而<code>vector</code>提供了随机访问迭代器，因此有：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">(vector&lt;<span class="hljs-type">int</span>&gt;::iterator vip, list&lt;string&gt;::iterator lsp)</span>
</span>{
    <span class="hljs-built_in">advance</span>(vip,<span class="hljs-number">10</span>); <span class="hljs-comment">// 应用快速的 advance()</span>
    <span class="hljs-built_in">advance</span>(lsp,<span class="hljs-number">10</span>); <span class="hljs-comment">// 应用缓慢的 advance()</span>
}
</code></pre>
<p>像其它类型的重载一样，这是个编译期机制，就是说没有运行时开销，
在编译器找不到最佳匹配时，会报二义性错误。
基于概束的重载，其规则远比普通重载(§1.3)简单。
首先考虑单个模板参数对应多个可选函数的情况：</p>
<ul>
<li>如果参数不匹配概束，该备选函数不选。</li>
<li>如果参数仅匹配一个备选函数，选它。</li>
<li>如果参数对两个备选函数匹配程度相等，就是二义性。</li>
<li>如果参数匹配两个备选，但一个概束比另一个更紧密
  （满足另一个函数全部需求还有富余），选前者。</li>
</ul>
<p>某个备选函数想被选中就必须：</p>
<ul>
<li>满足其全部参数，并且</li>
<li>至少跟其它备选的匹配同样好，并且</li>
<li>至少有一个参数匹配的更好。</li>
</ul>
<h3 id="7.2.3">7.2.3 代码有效性 </h3>
<p>有关模板实参是否满足模板对形参的需求这个问题，归根结底是某些表达式有效与否的问题。</p>
<p>使用<code>requires</code>表达式，可以检测一组表达式的有效性。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Forward_iterator Iter&gt;
<span class="hljs-type">void</span> <span class="hljs-title">advance</span><span class="hljs-params">(Iter p, <span class="hljs-type">int</span> n)</span> <span class="hljs-comment">// 将p向前移动n个元素</span>
</span>{
    <span class="hljs-keyword">while</span> (n--)
        ++p;                <span class="hljs-comment">// 前向迭代器可以++，但不能+或+=</span>
}
</code></pre>
<p><a class="en-page-number" id="97"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Forward_iterator Iter, <span class="hljs-type">int</span> n&gt;
    <span class="hljs-keyword">requires</span> <span class="hljs-title">requires</span><span class="hljs-params">(Iter p, <span class="hljs-type">int</span> i)</span> </span>{ p[i]; p+i; } <span class="hljs-comment">// Iter有取下标和加法操作</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">advance</span><span class="hljs-params">(Iter p, <span class="hljs-type">int</span> n)</span> <span class="hljs-comment">// 将p向前移动n个元素</span>
</span>{
    p+=n;                   <span class="hljs-comment">// 随机访问迭代器可以+=</span>
}
</code></pre>
<p>不，那个<code>requires requires</code>不是笔误。
前面的<code>requires</code>发起了一个<code>requirements</code>从句，
后面的<code>requires</code>发起了一个<code>requires</code>表达式</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">requires</span>(Iter p, <span class="hljs-type">int</span> i) { p[i]; p+i; }
</code></pre>
<p><code>requires</code>表达式是一个断言，若其内部的表达式是有效代码，
它就为<code>true</code>，无效则为<code>false</code>。</p>
<p>我把<code>requires</code>表达式看作是泛型编程的汇编代码。
像常规的汇编代码那样，<code>requires</code>表达式极其灵活并且没有为编程限定任何规则。
就某种形式而言，它们是大部分重要泛型代码的根基，
就像汇编码是大部分重要常规代码的根基那样。
像汇编码那样，<code>requires</code>表达式不应该在“常规代码”中出现。
如果你的代码里出现了 <code>requires requires</code>，很可能是把它的层级搞得太低了。</p>
<p><code>advance()</code>中的<code>requires requires</code>用法特意弄得粗劣和耍小聪明。
注意，我“忘记”要求<code>+=</code>，以及该运算所需的返回值类型。
尽量用命名概束，它们的名称暗含了语意方面的意义。勿谓言之不预也。</p>
<p>尽量采用命名良好的概束，它们具有定义准确的语意(§7.2.4)，
然后在定义这些概束的时候使用<code>requires</code>表达式。</p>
<h3 id="7.2.4">7.2.4 概束定义 </h3>
<p>到头来，我们想找到有用的概束，
比方说程序库尤其是标准库里的<code>Sequence</code>和<code>Arithmetic</code>。
区间技术细则（Ranges Technical Specification）[RangesTS]提供了一套，
用于约束标准库算法(§12.7)。不过，简单的概束不难定义。</p>
<p>概束是一个编译期的谓词，用于规定一个或多个类型该如何使用。
考虑这第一个最简单的例子：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">concept</span> Equality_comparable =
    <span class="hljs-built_in">requires</span> (T a, T b) {
        { a == b } -&gt; <span class="hljs-type">bool</span>; <span class="hljs-comment">// 用==比较两个T</span>
        { a != b } -&gt; <span class="hljs-type">bool</span>; <span class="hljs-comment">// 用!=比较两个T</span>
    };
</code></pre>
<p><code>Equality_comparable</code>这个概束的用途是：
确保能够对某个类型的值进行相等或不等比较。
我们直白地说，给定某个类型的两个值，它们必须可以用<code>==</code>和<code>!=</code>进行比较，
并且比较的结果必须能够转化到<code>bool</code>类型。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">static_assert</span>(Equality_comparable&lt;<span class="hljs-type">int</span>&gt;);    <span class="hljs-comment">// 成功</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">S</span> { <span class="hljs-type">int</span> a; };
<span class="hljs-built_in">static_assert</span>(Equality_comparable&lt;S&gt;);      <span class="hljs-comment">// 失败，因为结构体不会自动获得==和!=</span>
</code></pre>
<p><a class="en-page-number" id="98"></a></p>
<p>概束<code>Equality_comparable</code>的定义跟英语描述严格等同，并不冗长。
任何<code>concept</code>的值总是<code>bool</code>类型。</p>
<p>定义<code>Equality_comparable</code>去处理不同类型的比较，几乎同样易如反掌：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span> T2 =T&gt;
<span class="hljs-keyword">concept</span> Equality_comparable =
    <span class="hljs-built_in">requires</span> (T a, T2 b) {
        { a == b } -&gt; <span class="hljs-type">bool</span>; <span class="hljs-comment">// 把一个 T 和一个 T2 用 == 进行比较</span>
        { a != b } -&gt; <span class="hljs-type">bool</span>; <span class="hljs-comment">// 把一个 T 和一个 T2 用 != 进行比较</span>
        { b == a } -&gt; <span class="hljs-type">bool</span>; <span class="hljs-comment">// 把一个 T2 和一个 T 用 == 进行比较</span>
        { b != a } -&gt; <span class="hljs-type">bool</span>; <span class="hljs-comment">// 把一个 T2 和一个 T 用 != 进行比较</span>
    };
</code></pre>
<p><code>typename T2 =T</code>是说，如果没指明第二个模板参数，<code>T2</code>就和<code>T</code>一样，
<code>T</code>是<em>默认模板参数（default template argument）</em>。</p>
<p>可以这样测试<code>Equality_comparable</code>：</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">static_assert</span>(Equality_comparable&lt;<span class="hljs-type">int</span>,<span class="hljs-type">double</span>&gt;); <span class="hljs-comment">// 成功</span>
<span class="hljs-built_in">static_assert</span>(Equality_comparable&lt;<span class="hljs-type">int</span>&gt;);        <span class="hljs-comment">// 成功(T2默认是int)</span>
<span class="hljs-built_in">static_assert</span>(Equality_comparable&lt;<span class="hljs-type">int</span>,string&gt;); <span class="hljs-comment">// 失败</span>
</code></pre>
<p>对于更复杂的例子，考虑某个如下的序列：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> S&gt;
<span class="hljs-keyword">concept</span> Sequence = <span class="hljs-built_in">requires</span>(S a) {
    <span class="hljs-keyword">typename</span> Value_type&lt;S&gt;;             <span class="hljs-comment">// S必须具备一个值类型</span>
    <span class="hljs-keyword">typename</span> Iterator_type&lt;S&gt;;          <span class="hljs-comment">// S必须具备一个迭代器类型</span>

    { <span class="hljs-built_in">begin</span>(a) } -&gt; Iterator_type&lt;S&gt;;   <span class="hljs-comment">// begin(a) 必须返回一个迭代器</span>
    { <span class="hljs-built_in">end</span>(a) } -&gt; Iterator_type&lt;S&gt;;     <span class="hljs-comment">// end(a) 必须返回一个迭代器</span>

    <span class="hljs-keyword">requires</span> Same_type&lt;Value_type&lt;S&gt;,Value_type&lt;Iterator_type&lt;S&gt;&gt;&gt;;
    <span class="hljs-keyword">requires</span> Input_iterator&lt;Iterator_type&lt;S&gt;&gt;;
};
</code></pre>
<p>如果类型<code>S</code>要作为<code>Sequence</code>使用，必须提供<code>Value_type</code>（其元素的类型），
以及一个<code>iterator_type</code>（其迭代器的类型；参见§12.1）。
还要确保具备返回迭代器的<code>begin()</code>和<code>end()</code>函数，以符合标准库容器(§11.3)的惯例。
最后，<code>iterator_type</code>必须是个<code>input_iterator</code>，其元素与<code>S</code>元素的类型相同。</p>
<p>最难定义的概束是表示基本语言概念的那种。总而言之，最好从现存的库里拿一套出来用。
对于一套有用的概束集，参见§12.7。</p>
<h2 id="7.3">7.3 泛型编程 </h2>
<p>C++所支持的<em>泛型编程（generic programming）</em>形式，围绕着这样一个思想：
从具体、高效的算法抽象得出泛型算法，再把这个泛型算法跟多种数据形式结合，
继而生成各种有用的软件[Stepanov,2009]。
这种代表了基本运算和数据结构的抽象被称为<em>概束（concept）</em>；
它们的表现形式是对模板参数提出的条件。</p>
<p><a class="en-page-number" id="99"></a></p>
<h3 id="7.3.1">7.3.1 概束的使用 </h3>
<p>优秀、好用的概束（concept）是很基础的，多数情况下它们是被发现的，而非出于设计。
比如整数和浮点数（连古典的C语言中都有定义）、序列，
以及更通用的数学概念，例如域和向量空间。
它们代表某个应用领域里的基本概念。
这就是它们被称作“概束（concept）”的原因。
要识别概束并将其形式化，以达到高效泛型编程必要的程度，这极具挑战。</p>
<p>对于基本用法，考虑概束<code>Regular</code>(§12.7)。
某个类型如果是常规（regular）的，就要表现得像<code>int</code>或者<code>float</code>那样。
某个常规类型的对象要：</p>
<ul>
<li>可被默认构造</li>
<li>可用构造函数或者复制操作复制（以常见的复制语意，产生两个独立且相等的对象）</li>
<li>可用<code>==</code>和<code>!=</code>进行比较</li>
<li>不会因为过度耍小聪明的编程伎俩而招致技术问题</li>
</ul>
<p><code>string</code>是另一个常规类型的例子。
像<code>int</code>一样，<code>string</code>也是<code>StrictTotallyOrdered</code>的(§12.7)。
就是说，两个字符串可以用<code>&lt;</code>、<code>&lt;=</code>、<code>&gt;</code>和<code>&gt;=</code>进行语意良好的比较。</p>
<p>概束不仅是一个语法概念，根本上讲它事关语意。
比如说：别把<code>+</code>定义成除法；对于任何合理的数字系统，这都不符合要求。
不幸的是，在表达语意方面，我们尚无任何语言层面的支持，
因此只能依靠专业知识和直觉去获取暗合语意的概束。
不要定义语意上没意义的概束，比如<code>Addable</code>（可相加）和<code>Subtractable</code>（可相减）。
相反的，要在某个应用领域里，依靠该领域内的知识，去定义符合其基本概念的概束。</p>
<h3 id="7.3.2">7.3.2 利用模板进行抽象 </h3>
<p>良好的抽象是从具体例子中精心培育而来的。
试图给所有假想中的需求和技术做准备，并对其进行“抽象”是个馊主意；
必将导致粗鄙和代码膨胀。
相反，应该以一个——最好是多个——实际使用的具体例子为开端，
并努力去消除那些无关本质的细节。考虑：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">sum</span><span class="hljs-params">(<span class="hljs-type">const</span> vector&lt;<span class="hljs-type">int</span>&gt;&amp; v)</span>
</span>{
    <span class="hljs-type">double</span> res = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> x : v)
        res += x;
    <span class="hljs-keyword">return</span> res;
}
</code></pre>
<p>显而易见，这是诸多给数值序列求和的方法之一。</p>
<p>考虑一下，是什么让它变得不够通用：</p>
<ul>
<li>为什么只能是<code>int</code>？</li>
<li>为什么只能是<code>vector</code>？</li>
<li>为什么只能求和成一个<code>double</code>？</li>
<li>为什么只能从<code>0</code>开始计算？</li>
<li>为什么只能是加法？</li>
</ul>
<p>把具体类型换成模板参数，就可以回答前四个问题，
得出标准库算法<code>accumulate</code>最简单的形式：</p>
<p><a class="en-page-number" id="100"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Iter, <span class="hljs-keyword">typename</span> Val&gt;
Val <span class="hljs-title">accumulate</span><span class="hljs-params">(Iter first, Iter last, Val res)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p = first; p!=last; ++p)
        res += *p;
    <span class="hljs-keyword">return</span> res;
}
</code></pre>
<p>这样就得到：</p>
<ul>
<li>待遍历的数据结构被抽象成一对表示序列的迭代器(§12.1)。</li>
<li>待累加的类型被换成了参数。</li>
<li>初始值从输入获取；累加结果的类型就是初始值的类型。</li>
</ul>
<p>一个快捷的检验或者——更靠谱的——评测表明，
针对多种数据结构调用生成的代码与手动编码的原版一致。
例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">use</span><span class="hljs-params">(<span class="hljs-type">const</span> vector&lt;<span class="hljs-type">int</span>&gt;&amp; vec, <span class="hljs-type">const</span> list&lt;<span class="hljs-type">double</span>&gt;&amp; lst)</span>
</span>{
    <span class="hljs-keyword">auto</span> sum = <span class="hljs-built_in">accumulate</span>(<span class="hljs-built_in">begin</span>(vec),<span class="hljs-built_in">end</span>(vec),<span class="hljs-number">0.0</span>); <span class="hljs-comment">// 累加在一个double上</span>
    <span class="hljs-keyword">auto</span> sum2 = <span class="hljs-built_in">accumulate</span>(<span class="hljs-built_in">begin</span>(lst),<span class="hljs-built_in">end</span>(lst),sum);
    <span class="hljs-comment">//</span>
}
</code></pre>
<p>保留性能的同时，把一段具体代码（多段就更好了）泛化的过程叫做<em>提升（lifting）</em>。
反过来说，开发模板最好的办法通常是：</p>
<ul>
<li>首先，写一个具体的版本</li>
<li>然后，调试、测试，并评估其性能</li>
<li>最后，用模板参数替换具体类型</li>
</ul>
<p>自然而然的，不断重复<code>begin()</code>和<code>end()</code>很烦冗，因此可以把用户接口简化一点：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;Range R, Number Val&gt;   <span class="hljs-comment">// Range是具有begin()和end()的东西</span>
<span class="hljs-function">Val <span class="hljs-title">accumulate</span><span class="hljs-params">(<span class="hljs-type">const</span> R&amp; r, Val res = <span class="hljs-number">0</span>)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p = <span class="hljs-built_in">begin</span>(r); p!=<span class="hljs-built_in">end</span>(r); ++p)
        res += *p;
    <span class="hljs-keyword">return</span> res;
}
</code></pre>
<p>如果要彻底通用化，还可以抽象<code>+=</code>运算；参阅 §14.3。</p>
<h2 id="7.4">7.4 可变参数模板 </h2>
<p>模板可定义成接受任意数量任意类型参数的形式。
这样的模板叫<em>可变参数模板（variadic template）</em>。
考虑一个简单的函数，对于具有<code>&lt;&lt;</code>运算符的任意类型，它都能输出该类型的值。</p>
<p><a class="en-page-number" id="101"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;first: &quot;</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2.2</span>, <span class="hljs-string">&quot;hello\n&quot;</span>s); <span class="hljs-comment">// first: 1 2.2 hello</span>

    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\nsecond: &quot;</span>, <span class="hljs-number">0.2</span>, <span class="hljs-string">&apos;c&apos;</span>, <span class="hljs-string">&quot;yuck!&quot;</span>s, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-string">&apos;\n&apos;</span>); <span class="hljs-comment">// second: 0.2 c yuck! 0 1 2</span>
}
</code></pre>
<p>习惯上，实现可变参数模板的时候，要把第一个参数跟其余的分离开来，
然后为参数的尾部递归调用该可变参数模板：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-comment">// 对于无参数的这份：什么都不做</span>
}

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span>... Tail&gt;
<span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(T head, Tail... tail)</span>
</span>{
    <span class="hljs-comment">// 为每一个参数执行的，例如：</span>
    cout &lt;&lt; head &lt;&lt; <span class="hljs-string">&apos; &apos;</span>;
    <span class="hljs-built_in">print</span>(tail...);
}
</code></pre>
<p><code>typename...</code>表明<code>Tail</code>是一个类型多样的序列。
<code>Tail...</code>表明<code>tail</code>是一系列类型位于<code>Tail</code>中的值。
使用<code>...</code>声明的参数叫做<em>参数包（parameter pack）</em>。
此处<code>tail</code>是一个（函数参数的）参数包，
其元素类型可以在（模板参数的）参数包<code>Tail</code>中找到。
因此<code>print()</code>可以接收任意类型、任意数量的参数。</p>
<p>调用<code>print()</code>，会把参数分成头部（第一个）和尾部（其余的）。
头部会被输出，然后再为尾部调用<code>print()</code>。
到最后，理所当然地，<code>tail</code>就空了，于是就需要一个无参版本的<code>print()</code>去处理。
如果想避免 零参数 的情形，可以用一个编译期<code>if</code>去消除它：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span>... Tail&gt;
<span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(T head, Tail... tail)</span>
</span>{
    cout &lt;&lt; head &lt;&lt; <span class="hljs-string">&apos; &apos;</span>;
    <span class="hljs-function"><span class="hljs-keyword">if</span> <span class="hljs-title">constexpr</span><span class="hljs-params">(<span class="hljs-keyword">sizeof</span>...(tail)&gt; <span class="hljs-number">0</span>)</span>
        <span class="hljs-title">print</span><span class="hljs-params">(tail...)</span></span>;
}
</code></pre>
<p>我用了 编译期<code>if</code>(§6.4.3)，而非普通的 运行时<code>if</code>，
以避免生成最终那个“永不会被调用的<code>print()</code>”被生成出来。</p>
<p>可变参数模板（有时候称为<em>变参（variadic）</em>）的强大之处在于，
能够接收你交给它的任何参数。
其弱点包括：</p>
<ul>
<li>要把递归的实现搞对，这有点棘手</li>
<li>递归实现在编译期有出其不意的代价</li>
<li>接口的类型检查本身，就是一个疑似过度精细的模板规则</li>
</ul>
<p>由于其灵活性，可变参数模板被广泛用于标准库中，偶尔甚至被滥用了。</p>
<p><a class="en-page-number" id="102"></a></p>
<h3 id="7.4.1">7.4.1 折叠表达式 </h3>
<p>为简化简短变参模板的实现，C++17提供了一个针对参数包元素进行有限迭代的形式。
例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>... T&gt;
<span class="hljs-type">int</span> <span class="hljs-title">sum</span><span class="hljs-params">(T... v)</span>
</span>{
    <span class="hljs-keyword">return</span> (v + ... + <span class="hljs-number">0</span>);   <span class="hljs-comment">// 以0为初值，把v的所有元素相加</span>
}
</code></pre>
<p>此处，<code>sum()</code>可以接收任意类型的任意多个参数。
假设<code>sum()</code>确实会将其参数相加，则有：</p>
<pre><code class="lang-cpp"><span class="hljs-type">int</span> x = <span class="hljs-built_in">sum</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>); <span class="hljs-comment">// x 成为 15</span>
<span class="hljs-type">int</span> y = <span class="hljs-built_in">sum</span>(<span class="hljs-string">&apos;a&apos;</span>, <span class="hljs-number">2.4</span>, x);   <span class="hljs-comment">// y 成为 114（2.4被截断了，&apos;a&apos;的值是97）</span>
</code></pre>
<p><code>sum</code>的函数体使用了折叠表达式：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">return</span> (v + ... + <span class="hljs-number">0</span>);   <span class="hljs-comment">// 以0为初值，把v的所有元素相加</span>
</code></pre>
<p>此处，<code>(v+...+0)</code>意思是把<code>v</code>的所有元素相加，从初始值<code>0</code>开始执行。
第一个参与加法运算的是“最右边”（下标值最大）的值：
<code>(v[0]+(v[1]+(v[2]+(v[3]+(v[4]+0)))))</code>。
就是说，从右边，也就是<code>0</code>那个位置开始。这叫做<em>右叠（right fold）</em>。
或者，也可以用<em>左叠（left fold）</em>：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Number... T&gt;
<span class="hljs-type">int</span> <span class="hljs-title">sum2</span><span class="hljs-params">(T... v)</span>
</span>{
    <span class="hljs-keyword">return</span> (<span class="hljs-number">0</span> + ... + v);   <span class="hljs-comment">// 把v的所有元素加到0上</span>
}
</code></pre>
<p>这次，最先参与加法运算是“最左边”（下标值最小）的元素：
<code>(((((0+v[0])+v[1])+v[2])+v[3])+v[4])</code>。
就是说，从左边，也就是<code>0</code>那个位置开始。</p>
<p><em>折叠（fold）</em>是个非常强大的抽象，很明显与标准库的<code>accumulate()</code>有关联，
在不同的编程语言和社区里有多种名称。
在C++中，折叠表达式的使用，目前被限定在可变参数模板的简化方面。
折叠涉及的操作可以不是数值计算。考虑这个著名的例子：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> ...T&gt;
<span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(T&amp;&amp;... args)</span>
</span>{
    (std::cout &lt;&lt; ... &lt;&lt; args) &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>; <span class="hljs-comment">// 输出所有参数</span>
}
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Hello!&quot;</span>s,<span class="hljs-string">&apos; &apos;</span>,<span class="hljs-string">&quot;World &quot;</span>,<span class="hljs-number">2017</span>); <span class="hljs-comment">// (((((std::cout &lt;&lt; &quot;Hello!&quot;s) &lt;&lt; ’ ’) &lt;&lt; &quot;World &quot;) &lt;&lt; 2017) &lt;&lt; ’\n’);</span>
</code></pre>
<p>很多用例仅涉及一组值，且可以转化为一个通用类型。
这种情况下，直接把参数复制到一个vector或者期望的容器类型，通常可以简化后续使用：</p>
<p><a class="en-page-number" id="103"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Res, <span class="hljs-keyword">typename</span>... Ts&gt;
vector&lt;Res&gt; <span class="hljs-title">to_vector</span><span class="hljs-params">(Ts&amp;&amp;... ts)</span>
</span>{
    vector&lt;Res&gt; res; (res.<span class="hljs-built_in">push_back</span>(ts) ...);   <span class="hljs-comment">// 不需要初始值</span>
    <span class="hljs-keyword">return</span> res;
}
</code></pre>
<p>可以这样使用<code>to_vector</code>：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> x = <span class="hljs-built_in">to_vector</span>&lt;<span class="hljs-type">double</span>&gt;(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">4.5</span>,<span class="hljs-string">&apos;a&apos;</span>);

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span>... Ts&gt;
<span class="hljs-type">int</span> <span class="hljs-title">fct</span><span class="hljs-params">(Ts&amp;&amp;... ts)</span>
</span>{
    <span class="hljs-keyword">auto</span> args = <span class="hljs-built_in">to_vector</span>&lt;string&gt;(ts...);   <span class="hljs-comment">// args[i]是第i个参数</span>
    <span class="hljs-comment">// ... 在这里使用 args ...</span>
}

<span class="hljs-type">int</span> y = <span class="hljs-built_in">fct</span>(<span class="hljs-string">&quot;foo&quot;</span>, <span class="hljs-string">&quot;bar&quot;</span>, s);
</code></pre>
<h3 id="7.4.2">7.4.2 参数转发 </h3>
<p>通过接口把参数原封不动传递，是可变参数模板的重要用途。
考虑一个网络输入信道，其中输送具体值的方法是个参数。
不同的传送机制拥有各自的一套构造参数：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Transport&gt;
    <span class="hljs-keyword">requires</span> concepts::InputTransport&lt;Transport&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">InputChannel</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-comment">// ...</span>
    <span class="hljs-built_in">InputChannel</span>(TransportArgs&amp;&amp;... transportArgs)
        : _transport(std::forward&lt;TransportArgs&gt;(transportArgs)...) {}
    <span class="hljs-comment">// ...</span>
    Transport _transport;
};
</code></pre>
<p>标准库函数<code>forward()</code>(§13.2.2)被用于传递参数，
从<code>inputChannel</code>的构造函数原封不动地送给<code>Transport</code>构造函数。</p>
<p>此处的重点是，<code>inputChannel</code>的作者可以构造一个<code>Transport</code>类型的对象，
而无需知晓用于构造特定<code>Transport</code>所需的参数为何。
<code>inputChannel</code>的实现者仅需要知道所有<code>Transport</code>的通用接口。</p>
<p>前向操作在基础的函数库中十分常见，在那里通用性和较低运行时消耗是必要的，
而且极具通用性的接口很常见。</p>
<p><a class="en-page-number" id="104"></a></p>
<h2 id="7.5">7.5 模板编译模型 </h2>
<p>假定有概束(§7.2)支持，模板参数会针对其概束进行检查。
此处发现的错误会报出来，程序员必须修复这些问题。
目前尚不能检查的部分，比如无约束参数，会推迟到模板会同一组模板参数生成的时候：
“在模板实例化期间”。对于有概束支持以前的代码来说，在这里进行所有检查。
在使用概束的时候，只有概束检查通过，编译才能走到这儿。</p>
<p>实例化期间（较迟的）类型检验有个糟糕的副作用：
因为编译器要在信息从程序内多处汇集之后才能发现问题，
所以发现类型错误的时间特别迟，导致错误信息糟糕到令人发指。</p>
<p>模板的实例化期间，类型检查会对模板定义中的参数进行检查。
这提供了一个俗称<em>鸭子类型（duck type）</em>
（“如果它走路像鸭子，叫声像鸭子，那它就是个鸭子（
If it walks like a duck and it quacks like a duck, it’s a duck）”）
的编译期变种。
或者——用术语说——我们操作的是值，该操作的形式和意义都仅依赖于其操作数。
这有别于另一种观点，即对象具有类型，而类型决定了该操作的形式和意义。
值“居住”在对象里。
这就是C++里对象（也即变量）运作的方式，并且必须是符合该对象需求的值才住得进去。
在编译期用模板搞定的内容，基本不涉及对象，仅有值。
有个例外，是<code>constexpr</code>函数(§1.6)里的局部变量，它在编译器里作为变量使用。</p>
<p>要使用某个无约束模板，其定义（而不仅是其声明）必须位于它被用到的作用域内。
例如，标准库头文件<code>&lt;vector&gt;</code>里有<code>vector</code>的定义。
在实践中，这意味着模板定义一般位于头文件，而非<code>.cpp</code>文件。
这一点在使用模块(§3.3)的时候有些变化。
使用模块时，常规函数和模板函数的源代码的组织方式相同。
在两种情况下，定义都不会受到与文本包含有关问题的干扰。</p>
<h2 id="7.6">7.6 忠告 </h2>
<ul>
<li>[1] 模板提供了一个编译期编程的通用机制；§7.1。</li>
<li>[2] 设计模板的时候，针对其模板参数，谨慎考虑假定的概束（需求）；§7.3.2。</li>
<li>[3] 设计模板的时候，从一个具体的版本开始实现、调试及评估；§7.3.2。</li>
<li>[4] 把模板作为一个设计工具；§7.3.1。</li>
<li>[5] 为所有的模板参数指定概束；§7.2; [CG: T.10]。</li>
<li>[6] 尽可能使用标准概束（如：区间概束，Ranges concepts）；§7.2.4; [CG: T.11]。</li>
<li>[7] 如果需要个简单的一次性函数对象，用lambda表达式；§6.3.2。</li>
<li>[8] 模板没有分离编译：请在每个用到它的编译单元里<code>#include</code>模板定义</li>
<li>[9] 用模板表示容器和区间；§7.3.2; [CG: T.3]。</li>
<li>[10] 避免缺乏语意意义的“概束”；§7.2; [CG: T.20]。</li>
<li>[11] 为概束定义一组完善的操作；§7.2; [CG: T.21]。</li>
</ul>
<p><a class="en-page-number" id="105"></a></p>
<ul>
<li>[12] 在需要一个函数接收不同类型不定数量的参数时，采用可变参数模板；§7.4。</li>
<li>[13] 对于类型相同的参数列表，别用可变参数模板（这种情况尽量用初值列表）；§7.4。</li>
<li>[14] 使用模板时，确保其定义（而不仅仅是声明）在作用域内；§7.5。</li>
<li>[15] 模板提供编译期的“鸭子类型”；§7.5。</li>
</ul>
<blockquote id="fn_1">
<sup>1</sup>. 出自一份意大利杂志《INFOMEDIA》对 Alexander A. Stepanov 的访谈录，访谈内容全文位于 <a href="http://www.stlport.org/resources/StepanovUSA.html" target="_blank">http://www.stlport.org/resources/StepanovUSA.html</a>。—— 译者注<a href="#reffn_1" title="Jump back to footnote [1] in the text."> ↩</a>
</blockquote>

<script>console.log(window.location.pathname)</script>
<div id="disqus_thread"></div>
<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = window.location.href;
this.page.identifier = window.location.pathname;
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://a-tour-of-cpp-2nd-cn.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" target="_blank">comments powered by Disqus.</a></noscript>
                                
    

                                </section>
                            
    </div>
    <div class="search-results">
        <div class="has-results">
            
            <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
            <ul class="search-results-list"></ul>
            
        </div>
        <div class="no-results">
            
            <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
            
        </div>
    </div>
</div>

                        </div>
                    </div>
                
            </div>

            
                
                <a href="ch06.html" class="navigation navigation-prev " aria-label="Previous page: 6 模板">
                    <i class="fa fa-angle-left"></i>
                </a>
                
                
                <a href="ch08.html" class="navigation navigation-next " aria-label="Next page: 8 标准库概览">
                    <i class="fa fa-angle-right"></i>
                </a>
                
            
        
    </div>

    <script>
        var gitbook = gitbook || [];
        gitbook.push(function() {
            gitbook.page.hasChanged({"page":{"ch":7,"title":"7 概束和泛型编程","level":"1.9","depth":1,"next":{"title":"8 标准库概览","level":"1.10","depth":1,"path":"ch08.md","ref":"ch08.md","articles":[]},"previous":{"title":"6 模板","level":"1.8","depth":1,"path":"ch06.md","ref":"ch06.md","articles":[]},"dir":"ltr"},"config":{"plugins":["@dogatana/page-toc-button","@dogatana/back-to-top-button","copy-code-button","forkmegithub","disqus-legacy"],"root":"./src","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"@dogatana/back-to-top-button":{},"styles":{"website":"styles/website.css"},"search":{},"@dogatana/page-toc-button":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"fontsettings":{"theme":"white","family":"sans","size":2},"highlight":{},"disqus-legacy":{"shortname":"a-tour-of-cpp-2nd-cn"},"copy-code-button":{},"forkmegithub":{"color":"orange","url":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"Windsting","pdf":{"pageNumbers":true,"fontSize":14,"fontFamily":"Arial","paperSize":"a5","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"C++导览 第二版 简体中文版","language":"zh-hans","links":{"sidebar":{"Github Link":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"}},"gitbook":"*","description":"A Tour of C++ (第二版) 非官方中译本"},"file":{"path":"ch07.md","mtime":"2023-02-12T09:23:08.801Z","type":"markdown"},"gitbook":{"version":"5.1.1","time":"2023-10-27T09:54:29.738Z"},"basePath":".","book":{"language":""}});
        });
    </script>
</div>

        
    <noscript>
        <style>
            .honkit-cloak {
                display: block !important;
            }
        </style>
    </noscript>
    <script>
        // Restore sidebar state as critical path for prevent layout shift
        function __init__getSidebarState(defaultValue){
            var baseKey = "";
            var key = baseKey + ":sidebar";
            try {
                var value = localStorage[key];
                if (value === undefined) {
                    return defaultValue;
                }
                var parsed = JSON.parse(value);
                return parsed == null ? defaultValue : parsed;
            } catch (e) {
                return defaultValue;
            }
        }
        function __init__restoreLastSidebarState() {
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            if (isMobile) {
                // Init last state if not mobile
                return;
            }
            var sidebarState = __init__getSidebarState(true);
            var book = document.querySelector(".book");
            // Show sidebar if it enabled
            if (sidebarState && book) {
                book.classList.add("without-animation", "with-summary");
            }
        }

        try {
            __init__restoreLastSidebarState();
        } finally {
            var book = document.querySelector(".book");
            book.classList.remove("honkit-cloak");
        }
    </script>
    <script src="gitbook/gitbook.js"></script>
    <script src="gitbook/theme.js"></script>
    
        
        <script src="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.js"></script>
        
    
        
        <script src="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-copy-code-button/toggle.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-forkmegithub/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search-engine.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
        
    

    </body>
</html>

